﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using JTLeigh.Commerce.Spencer.Core.Common;
using JTLeigh.Commerce.Spencer.Core.Common.Entities;

namespace JTLeigh.Commerce.Spencer.Core.Business
{

	/// <summary>
	/// <see langword="static"/> methods for working with relations' associations and 
	/// disassociations.
	/// </summary>
	public static class RelationsServiceTools
	{

		#region Static Methods

		/// <summary>
		/// Gets and merges the associations and disassociations for a list of categories.
		/// </summary>
		/// <typeparam name="TAssociation">An association class inheriting from
		/// <see cref="AssociationBase"/>.</typeparam>
		/// <typeparam name="TDisassociation">A disassociation class inheriting from
		/// <see cref="DisassociationBase"/>.</typeparam>
		/// <param name="getAssociations">The <see cref="GetCategoryAssociations{TAssociation}"/> 
		/// delegate to call to get the associations for each category.</param>
		/// <param name="getDisassociations">The <see cref="GetCategoryDisassociations{TDisassociation}"/>
		/// delegate to call to get the disassociations for each category.</param>
		/// <param name="categoryIds">An <see cref="IList{TEntry}"/> of the category identifiers
		/// of the categories to get and merge associations and disassociations for.</param>
		/// <param name="effectiveDate">The date used as the current date when deciding whether to 
		/// merge an association or disassociation.</param>
		/// <returns>A <see cref="Dictionary{TKey,TValue}"/> of <typeparamref name="TAssociation"/>
		/// containing the merged associations for the specified categories.</returns>
		public static Dictionary<Guid, TAssociation> GetAssociationsForCategories<TAssociation, TDisassociation>(GetCategoryAssociations<TAssociation> getAssociations, GetCategoryDisassociations<TDisassociation> getDisassociations, IList<string> categoryIds, DateTime effectiveDate)
			where TAssociation : AssociationBase
			where TDisassociation : DisassociationBase
		{
			Collection<TAssociation> sourceAssociations;
			Collection<TDisassociation> sourceDisassociations;
			Dictionary<Guid, TAssociation> associations;

			Safety.AssertArgumentNotNull("getAssociations", getAssociations);
			Safety.AssertArgumentNotNull("getDisassociations", getAssociations);
			Safety.AssertArgumentNotNull("categoryIds", categoryIds);

			associations = new Dictionary<Guid, TAssociation>();
			foreach (string categoryId in categoryIds)
			{
				sourceAssociations = getAssociations(categoryId);
				sourceDisassociations = getDisassociations(categoryId);
				MergeAssociationsAndDisassociations<TAssociation, TDisassociation, TAssociation>(sourceAssociations, sourceDisassociations, associations, effectiveDate);
			}
			return associations;
		}

		/// <summary>
		/// Merges collections of associations and disassociations into a dictionary of 
        /// associations using the merge rules defined in <see cref="RelationsServiceTools.MergeAssociation"/>
		/// and <see cref="RelationsServiceTools.MergeDisassociation"/>.
		/// </summary>
		/// <typeparam name="TSourceAssociation">An association class inheriting from 
        /// <see cref="AssociationBase"/>.</typeparam>
		/// <typeparam name="TSourceDisassociation">A disassociation class inheriting from 
        /// <see cref="DisassociationBase"/>.</typeparam>
		/// <typeparam name="TDestination">An association class inheriting from 
		/// <see cref="AssociationBase"/>.</typeparam>
		/// <param name="sourceAssociations">The <see cref="Collection{TEntry}"/> of 
		/// <typeparamref name="TSourceAssociation"/> to merge.</param>
		/// <param name="sourceDisassociations">The <see cref="Collection{TEntry}"/> of
		/// <typeparamref name="TSourceDisassociation"/> to merge.</param>
		/// <param name="destinationAssociations">The <see cref="Dictionary{TKey,TValue}"/> of 
		/// <typeparamref name="TDestination"/> to merge the associations and disassociations
		/// into.</param>
		/// <param name="effectiveDate">The date used as the current date when deciding whether to 
		/// merge an association.</param>
		public static void MergeAssociationsAndDisassociations<TSourceAssociation, TSourceDisassociation, TDestination>(Collection<TSourceAssociation> sourceAssociations, Collection<TSourceDisassociation> sourceDisassociations, Dictionary<Guid, TDestination> destinationAssociations, DateTime effectiveDate)
			where TSourceAssociation : AssociationBase, TDestination
			where TSourceDisassociation : DisassociationBase
			where TDestination : AssociationBase
		{
			Safety.AssertArgumentNotNull("sourceAssociations", sourceAssociations);
			Safety.AssertArgumentNotNull("sourceDisassociations", sourceDisassociations);
			Safety.AssertArgumentNotNull("destinationAssociations", destinationAssociations);

			foreach (TSourceAssociation currentAssociation in sourceAssociations)
			{
				MergeAssociation<TSourceAssociation, TDestination>(currentAssociation, destinationAssociations, effectiveDate);
			}
			foreach (TSourceDisassociation currentDisassociation in sourceDisassociations)
			{
				MergeDisassociation<TSourceDisassociation, TDestination>(currentDisassociation, destinationAssociations, effectiveDate);
			}
		}

		/// <summary>
		/// Merges a dictionary of associations into another dictionary of associations using the
		/// merge rules defined in <see cref="RelationsServiceTools.MergeAssociation"/>.
		/// </summary>
		/// <typeparam name="TSource">An association class inheriting from 
		/// <see cref="AssociationBase"/>.</typeparam>
		/// <typeparam name="TDestination">An association class inheriting from 
		/// <see cref="AssociationBase"/>.</typeparam>
		/// <param name="sourceAssociations">The <see cref="Dictionary{TKey,TValue}"/> of 
		/// <typeparamref name="TSource"/> to merge.</param>
		/// <param name="destinationAssociations">The <see cref="Dictionary{TKey,TValue}"/> of 
		/// <typeparamref name="TDestination"/> to merge the associations into.</param>
		/// <param name="effectiveDate">The date used as the current date when deciding whether to 
		/// merge an association.</param>
		public static void MergeAssociations<TSource, TDestination>(Dictionary<Guid, TSource> sourceAssociations, Dictionary<Guid, TDestination> destinationAssociations, DateTime effectiveDate)
			where TSource : AssociationBase, TDestination
			where TDestination : AssociationBase
		{
			Safety.AssertArgumentNotNull("sourceAssociations", sourceAssociations);
			Safety.AssertArgumentNotNull("destinationAssociations", destinationAssociations);

			foreach (TSource currentAssociation in sourceAssociations.Values)
			{
				MergeAssociation<TSource, TDestination>(currentAssociation, destinationAssociations, effectiveDate);
			}
		}

		/// <summary>
		/// Merges an association into a dictionary of associations.
		/// </summary>
		/// <typeparam name="TSource">An association class inheriting from 
		/// <see cref="AssociationBase"/>.</typeparam>
		/// <typeparam name="TDestination">An association class inheriting from 
		/// <see cref="AssociationBase"/>.</typeparam>
		/// <param name="sourceAssociation">The <typeparamref name="TSource"/> to merge into the
		/// dictionary.</param>
		/// <param name="destinationAssociations">The <see cref="Dictionary{TKey,TValue}"/> of 
		/// <typeparamref name="TDestination"/> to merge the sassociation into.</param>
		/// <param name="effectiveDate">The date used as the current date when deciding whether to 
		/// merge an association.</param>
		/// <remarks>
		/// <para>
		/// If an association has started on or before the effective date, and either has no finish
		/// date or has not finished before the effective date, it will be considered for merging.
		/// Otherwise, the association will be ignored.
		/// </para>
		/// <para>
		/// If the dictionary does not already contain an association with the same associated 
		/// item identifier as the association being considered for merging, the association will 
        /// be added to the dictionary.  If the dictionary does already contain an association with
		/// the same associated item identifier, and if the association in the dictionary started 
        /// before the association being considered, the association in the dictionary will be 
        /// replaced with the association being considered.  Otherwise, the association being 
        /// considered will be ignored.
		/// </para>
		/// </remarks>
		public static void MergeAssociation<TSource, TDestination>(TSource sourceAssociation, Dictionary<Guid, TDestination> destinationAssociations, DateTime effectiveDate)
			where TSource : AssociationBase, TDestination
			where TDestination : AssociationBase
		{
			TDestination bestAssociation;

			Safety.AssertArgumentNotNull("sourceAssociation", sourceAssociation);
			Safety.AssertArgumentNotNull("destinationAssociations", destinationAssociations);

			if ((sourceAssociation.StartDate <= effectiveDate) && ((!sourceAssociation.FinishDate.HasValue) || (sourceAssociation.FinishDate.Value > effectiveDate)))
			{
				if (destinationAssociations.TryGetValue(sourceAssociation.AssociatedItemId, out bestAssociation))
				{
					if (sourceAssociation.StartDate > bestAssociation.StartDate)
					{
						destinationAssociations[sourceAssociation.AssociatedItemId] = sourceAssociation;
					}
				}
				else
				{
					destinationAssociations.Add(sourceAssociation.AssociatedItemId, sourceAssociation);
				}
			}
		}

		/// <summary>
		/// Merges a disassociation into a dictionary of associations.
		/// </summary>
		/// <typeparam name="TSource">A disassociation class inheriting from 
		/// <see cref="DisassociationBase"/>.</typeparam>
		/// <typeparam name="TDestination">An association class inheriting from 
		/// <see cref="AssociationBase"/>.</typeparam>
		/// <param name="sourceDisassociation">The <typeparamref name="TSource"/> to merge into the
		/// dictionary.</param>
		/// <param name="destinationAssociations">The <see cref="Dictionary{TKey,TValue}"/> of 
		/// <typeparamref name="TDestination"/> to merge the disassociation into.</param>
		/// <param name="effectiveDate">The date used as the current date when deciding whether to 
		/// merge a disassociation.</param>
		/// <remarks>
		/// <para>
		/// If a disassociation has started on or before the effective date, and either has no 
		/// finish date or has not finished before the effective date, the association with the 
		/// same associated item identifier will be removed from the dictionary.  Otherwise, the 
		/// disassociation will be ignored.
		/// </para>
		/// </remarks>
		public static void MergeDisassociation<TSource, TDestination>(TSource sourceDisassociation, Dictionary<Guid, TDestination> destinationAssociations, DateTime effectiveDate)
			where TSource : DisassociationBase
			where TDestination : AssociationBase
		{
			Safety.AssertArgumentNotNull("currentDisassociation", sourceDisassociation);
			Safety.AssertArgumentNotNull("destinationAssociations", destinationAssociations);

			if ((sourceDisassociation.StartDate <= effectiveDate) && ((!sourceDisassociation.FinishDate.HasValue) || (sourceDisassociation.FinishDate.Value > effectiveDate)))
			{
				destinationAssociations.Remove(sourceDisassociation.DisassociatedItemId);
			}
		}

		#endregion // Static Methods

	}

}
